/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

package ejb.bean;

import ejb.attribute.ParSearchDoubleRange;
import ejb.attribute.ParSearchEqual;
import ejb.attribute.ParSearchString;
import ejb.attribute.ResultRange;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

/**
 *
 * @author Nothing4ever
 */
public abstract class AbstractFacade<T> {
    private Class<T> entityClass;

    public AbstractFacade(Class<T> entityClass) {
        this.entityClass = entityClass;
    }

    protected abstract EntityManager getEntityManager();

    public void create(T entity) {
        getEntityManager().persist(entity);
    }

    public void edit(T entity) {
        getEntityManager().merge(entity);
    }

    public void remove(T entity) {
        getEntityManager().remove(getEntityManager().merge(entity));
    }

    public T find(Object id) {
        return getEntityManager().find(entityClass, id);
    }

    public List<T> findAll() {
        javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
        cq.select(cq.from(entityClass));
        return getEntityManager().createQuery(cq).getResultList();
    }

    public List<T> findRange(ResultRange resultRange) {
        javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
        cq.select(cq.from(entityClass));
        javax.persistence.Query q = getEntityManager().createQuery(cq);
        q.setMaxResults(resultRange.getMaxResult());
        q.setFirstResult(resultRange.getFirstResult());
        return q.getResultList();
    }

    public int count() {
        javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
        javax.persistence.criteria.Root<T> rt = cq.from(entityClass);
        cq.select(getEntityManager().getCriteriaBuilder().count(rt));
        javax.persistence.Query q = getEntityManager().createQuery(cq);
        return ((Long) q.getSingleResult()).intValue();
    }
    
    public List<T> searchBasic(T t, ResultRange resultRange) {
        try {
            final CriteriaBuilder criteriaBuilder = getEntityManager().getCriteriaBuilder();
            CriteriaQuery<T> criteriaQuery = criteriaBuilder.createQuery(entityClass);
            Root<T> root = criteriaQuery.from(entityClass);
            criteriaQuery.select(root);
            Field[] fields = t.getClass().getDeclaredFields();
            ArrayList<Predicate> arrPredicate = new ArrayList<Predicate>();
            for (Field field : fields) {
                field.setAccessible(true);
                final String fieldName = field.getName();
                if (fieldName.equalsIgnoreCase("serialVersionUID")) {
                    continue;
                } else if (fieldName.contains("persistence")) {
                    continue;
                }
                Object value = field.get(t);
                if (value != null) {
                    if (value.getClass().getCanonicalName().equals("java.lang.String")) {
                        arrPredicate.add(criteriaBuilder.or(criteriaBuilder.like(root.<String>get(fieldName), "%" + value + "%"),
                                criteriaBuilder.isNull(root.get(fieldName))));
                    } else {
                        arrPredicate.add(criteriaBuilder.or(criteriaBuilder.equal(root.get(fieldName), value),
                                criteriaBuilder.isNull(root.get(fieldName))));
                    }
                }

            }
            criteriaQuery.where(criteriaBuilder.and(arrPredicate.toArray(new Predicate[0])));
            TypedQuery<T> typedQuery = getEntityManager().createQuery(criteriaQuery);
            setResultRange(typedQuery, resultRange);
            return typedQuery.getResultList();
        } catch (Exception e) {
            e.printStackTrace();
        }
//        return getEntityManager().find(entityClass, id,properties);
        return null;
    }

    protected List<T> getByDoubleRange(String fieldName, double min, double max, ResultRange resultRange) {
        CriteriaBuilder criteriaBuilder = getEntityManager().getCriteriaBuilder();
        CriteriaQuery<T> criteriaQuery = criteriaBuilder.createQuery(entityClass);
        Root<T> rootHouse = criteriaQuery.from(entityClass);
        criteriaQuery.select(rootHouse);
        final Path<Double> pathPrice = rootHouse.<Double>get(fieldName);
        ArrayList<Predicate> arrPredicate = new ArrayList<Predicate>();
        arrPredicate.add(criteriaBuilder.greaterThanOrEqualTo(pathPrice, min));
        arrPredicate.add(criteriaBuilder.lessThanOrEqualTo(pathPrice, max));
        criteriaQuery.where(criteriaBuilder.and(arrPredicate.toArray(new Predicate[0])));
        TypedQuery<T> typedQuery = getEntityManager().createQuery(criteriaQuery);
        setResultRange(typedQuery, resultRange);
        return typedQuery.getResultList();
    }

    public List<T> searchAdvance(List<ParSearchString> listParSearchString,
            List<ParSearchDoubleRange> listParSearchDoubleRange,
            List<ParSearchEqual> listParSearchEquals,
            ResultRange resultRange) {

        CriteriaBuilder criteriaBuilder = getEntityManager().getCriteriaBuilder();
        CriteriaQuery<T> criteriaQuery = criteriaBuilder.createQuery(entityClass);
        Root<T> root = criteriaQuery.from(entityClass);
        criteriaQuery.select(root);
        ArrayList<Predicate> arrPredicate = new ArrayList<Predicate>();
        if (listParSearchDoubleRange != null) {
            for (ParSearchDoubleRange parSearchDoubleRange : listParSearchDoubleRange) {
                final Path<Double> pathPrice = root.<Double>get(parSearchDoubleRange.getFieldName());
                arrPredicate.add(criteriaBuilder.greaterThanOrEqualTo(pathPrice, parSearchDoubleRange.getMinValue()));
                arrPredicate.add(criteriaBuilder.lessThanOrEqualTo(pathPrice, parSearchDoubleRange.getMaxValue()));
            }
        }
        if (listParSearchString != null) {
            for (ParSearchString parSearchString : listParSearchString) {
                arrPredicate.add(criteriaBuilder.or(
                        criteriaBuilder.like(root.<String>get(parSearchString.getFieldName()),
                        "%" + parSearchString.getFieldValue() + "%"),
                        criteriaBuilder.isNull(root.get(parSearchString.getFieldName()))));
            }
        }
        if (listParSearchEquals != null) {
            for (ParSearchEqual parSearchEqual : listParSearchEquals) {
                arrPredicate.add(criteriaBuilder.or(
                        criteriaBuilder.equal(root.get(parSearchEqual.getFiledName()), parSearchEqual.getValue())));                
            }
        }
        criteriaQuery.where(criteriaBuilder.and(arrPredicate.toArray(new Predicate[0])));
        TypedQuery<T> typedQuery = getEntityManager().createQuery(criteriaQuery);
        setResultRange(typedQuery, resultRange);
        return typedQuery.getResultList();
    }

    protected List<T> getByIntegerRange(String filedName, int min, int max, ResultRange resultRange) {
        return getByDoubleRange(filedName, min, max, resultRange);
    }

    
    private void setResultRange(TypedQuery<T> typedQuery, ResultRange resultRange) {
        if (resultRange != null) {
            if (resultRange.getFirstResult() > 0 && resultRange.getMaxResult() > 0) {
                typedQuery.setFirstResult(resultRange.getFirstResult());
                typedQuery.setMaxResults(resultRange.getMaxResult());
            }
        }
    }
}
